/* Copyright (c) 2006, Jan Flora <janflora@diku.dk>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without modification,
 * are permitted provided that the following conditions are met:
 *
 *  - Redistributions of source code must retain the above copyright notice, this
 *    list of conditions and the following disclaimer.
 *  - Redistributions in binary form must reproduce the above copyright notice,
 *    this list of conditions and the following disclaimer in the documentation
 *    and/or other materials provided with the distribution.
 *  - Neither the name of the University of Copenhagen nor the names of its
 *    contributors may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
 * SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
 * OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
 * TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
 * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/*
  @author Jan Flora <janflora@diku.dk>
*/

#include "mac.h"
#include "MacSuperframes.h"
#include "MacPib.h"

module BeaconGeneratorM
{
	#ifdef BEACON_SUPPORT
	provides
	{
		interface IeeeIndication<Mlme_SyncLossIndication> as MlmeIndicationSyncLoss;
		interface BeaconGenerator;
		interface CapEvents as CoordinatorCap;
		#ifdef GTS_SUPPORT
			interface CapEvents as CoordinatorCfp;
		#endif
		//interface Reset;
	}
	uses
	{
		interface CapTx as CoordinatorTx;
		interface RxFrame as BeaconReqFrame;
		interface MemoryManager as BufferMng;
		interface AsyncAlarm<time_t> as BeaconAlarm;
		interface BeaconDataService;
		#ifdef GTS_SUPPORT
			interface BeaconGtsService;
			interface AsyncAlarm<time_t> as CfpAlarm;
		#endif
		interface FrameTx;
		interface LocalTime<time_t> as LocalTime;
		interface Superframe;
		//interface PanConflict;
		interface MacAddress;
		interface MacFrame;
		interface Debug;
	}
	#else
	uses
	{
		interface CapTx as CoordinatorTx;
		interface RxFrame as BeaconReqFrame;
		interface MemoryManager as BufferMng;
		interface Superframe;
		interface MacAddress;
		interface MacFrame;
		interface Debug;
	}
	#endif
}
implementation
{
	// Beacon transmissions do not overlap, so there
	// is no race conditions on shared variables.
	#define DBG_LEVEL 1
	#include "Debug.h"
	
	uint8_t *myBeacon;
	// New approach.
	macMhr_t beaconMhr;
	macBeaconPayload_t beaconPayload;
	
	#ifdef BEACON_SUPPORT
	norace time_t nextCommence;
	norace time_t capEnd;
	time_t cfpEnd;
	norace bool waitingForCapEnd = FALSE;
	bool waitingForCfpEnd = FALSE;

	norace txdata_t myTxData;
	norace bool firstBeacon = FALSE;
	
	void transmitBeacon();
	#endif
	
	void generateBeacon();
	
	
	#ifdef BEACON_SUPPORT
		command result_t BeaconGenerator.start()
		{
			if (macPanCoordinator) {
				firstBeacon = TRUE;
				atomic coordinatorSuperframe.capLength = 16;
				generateBeacon();
				transmitBeacon();
			} else {
				// Align beacon to PAN Coordinator beacon.
			}
			return SUCCESS;
		}
		
		command result_t BeaconGenerator.stop()
		{
			return SUCCESS;
		}
	#endif
	
	async event uint8_t *BeaconReqFrame.received(rxdata_t *data)
	{
		bool isBeaconEnabled;
		atomic isBeaconEnabled = macBeaconEnabled;
		if (!isBeaconEnabled) {
			// Transmit beacon in CAP.
			txHeader_t *myTxHeader;
			
			generateBeacon();
			
			// Allocate the txHeader.
			if (SUCCESS != call BufferMng.claim(sizeof(txHeader_t), (uint8_t**)&myTxHeader)) {
				DBG_STR("FATAL: Associate, could not claim memory for transmission header",1);
			}
			
			// Build the txHeader.
			myTxHeader->addDsn = FALSE;
			myTxHeader->frame = myBeacon;
			//myTxHeader->length = mhrLengthFrame(myBeacon) + msduLength;
			myTxHeader->length = call MacFrame.getMhrSize(&beaconMhr) + call MacFrame.getBeaconSize(&beaconPayload);
			myTxHeader->isData = FALSE;

			call CoordinatorTx.sendFrame(myTxHeader);
		}
		return data->frame;
	}
	
	event void CoordinatorTx.done(txHeader_t *header)
	{
		// Release frame and tx header.
		call BufferMng.release(126, header->frame);
		call BufferMng.release(sizeof(txHeader_t), (uint8_t*)header);
	}
	
	void generateBeacon()
	{
		// Here we generate a beacon msg.
		ieeeAddress_t srcAddr;
		uint8_t descriptorCount = 0;
		uint8_t numShortAddrs = 0;
		uint8_t numExtAddrs = 0;
		
		// Allocate memory for the beacon frame.
		if (SUCCESS != call BufferMng.claim(126,&myBeacon)) {
			DBG_STR("WARNING: BeaconGenerator, Unable to claim buffer for beacon frame!",1);
			return;
		}
		
		call MacFrame.initializeMhrStruct(&beaconMhr, myBeacon);
		
		// First we clear the frame control bytes. Only the src addressing mode needs to be set
		// there! (Beacon frame = 0x00).
		*((uint16_t*)beaconMhr.frameControl) = 0;

		// Set sequence number
		atomic {
			*(beaconMhr.seqNum) = macBsn;
			macBsn++;
		}

		// Set source address.
		call MacAddress.getLocalAddr(&srcAddr, FALSE);
		call MacAddress.setAddrs(&beaconMhr, &srcAddr, NULL);
		
		call MacFrame.intializeBeaconStruct(&beaconPayload, beaconMhr.payload);
		
		// Create the beacon msdu content.
		
		// Fill GTS spec.
		// NOTE: GTS specs needs to be set first, because the GTS publishing can
		//       alter the capLength;
		#ifdef GTS_SUPPORT
		call BeaconGtsService.getPublishedGts(&(descriptorCount), (uint8_t*)(beaconPayload.gtsDirs));
		#endif
		beaconPayload.gtsSpec->GTSDescriptorCount = descriptorCount;
		beaconPayload.gtsSpec->GTSPermit = macGtsPermit;
		
		// Fill in superframe spec.
		beaconPayload.sfSpec->BeaconOrder = macBeaconOrder;
		beaconPayload.sfSpec->SuperframeOrder = macSuperframeOrder;
		atomic beaconPayload.sfSpec->FinalCAPSlot = coordinatorSuperframe.capLength - 1;
		beaconPayload.sfSpec->BatteryLifeExtension = macBattLifeExt;
		beaconPayload.sfSpec->PANCoordinator = macPanCoordinator;
		beaconPayload.sfSpec->AssociationPermit = macAssociationPermit;
		
		// Update payload struct to correct pending address pointers.
		call MacFrame.refreshBeaconStruct(&beaconPayload);
		
		// Fill pending addresses.
		#ifdef BEACON_SUPPORT	
		call BeaconDataService.getPendingAddrs(&(numShortAddrs),
		                                       &(numExtAddrs),
		                                       beaconPayload.addrList);
		#endif
		beaconPayload.addrSpec->NumShortAddrsPending = numShortAddrs;
		beaconPayload.addrSpec->NumExtAddrsPending = numExtAddrs;
		
		// Update payload struct to correct pending address pointers.
		call MacFrame.refreshBeaconStruct(&beaconPayload);
		
		// Append beacon payload.
		memcpy(beaconPayload.payload, macBeaconPayload, macBeaconPayloadLength);
	}
	
	#ifdef BEACON_SUPPORT
		void transmitBeacon()
		{
			// Transmit the frame.
			myTxData.frame = myBeacon;
			//myTxData.length = mhrLengthFrame(myBeacon) + msduLength;
			myTxData.length = call MacFrame.getMhrSize(&beaconMhr) + call MacFrame.getBeaconSize(&beaconPayload) + macBeaconPayloadLength;
			myTxData.cca = FALSE;
			myTxData.immediateCommence = FALSE;
			// If first beacon, commence 100 symbol periods from now.
			if (firstBeacon) {
				firstBeacon = FALSE;
				myTxData.commenceTime = call LocalTime.getTime() + 100;
			} else {
				myTxData.commenceTime = nextCommence;
			}
			
			if (PHY_SUCCESS != call FrameTx.tx(&myTxData)) {
				DBG_STR("WARNING: BeaconGenerator, could not transmit beacon!",1);
				// Release the memory for the beacon frame.
				call BufferMng.release(126, myBeacon);
				return;
			}		
			// Update coordinator superframe.
			//call Superframe.updateFromSpec(&coordinatorSuperframe, sfSpec,
			//                               myTxData.commenceTime, myTxData.length);
	
			call Superframe.updateFromSpec(&coordinatorSuperframe, beaconPayload.sfSpec,
			                               myTxData.commenceTime, myTxData.length);
	
			atomic macBeaconTxTime = myTxData.commenceTime;
			
			nextCommence = call Superframe.getNextStart(&coordinatorSuperframe);
			
			// Set up the beacon alarm to fire just before next beacon transmission
			call BeaconAlarm.armAlarmClock(nextCommence-100);
		}
		
		async event void FrameTx.txDone(phy_error_t error)
		{
			// Release the memory for the beacon frame.
			call BufferMng.release(126, myBeacon);
			if (error == PHY_SUCCESS) {
				atomic coordCapActive = TRUE;
				signal CoordinatorCap.startNotification();
				
				#ifdef GTS_SUPPORT
					capEnd = call Superframe.getCapEnd(&coordinatorSuperframe);
					// We end the cap 82 symbols before real cap end.
					// This is due to the fact, that the shortest tx packet takes
					// 30 symbols to transmit. Including both 2*CCA + SIFS makes 82 symbols.
					if (capEnd < nextCommence) {
						// We need to activate CFP or idle period at CAP end.
						call CfpAlarm.armAlarmClock(capEnd-82);
						waitingForCapEnd = TRUE;
					}
				#endif
				// Expire old indirect tx slots.
				call BeaconDataService.expireSlots();
			}
		}
		
		async event result_t BeaconAlarm.alarm()
		{
			// Time to transmit beacon.
			coordCapActive = FALSE;
			generateBeacon();
			transmitBeacon();
			return SUCCESS;
		}
		
		#ifdef GTS_SUPPORT
			async event result_t CfpAlarm.alarm()
			{
				if (waitingForCapEnd) {
					// CAP has ended.
					coordCapActive = FALSE;
					if (call Superframe.cfpExists(&coordinatorSuperframe)) {
						// We start up the coordinator CFP.
						signal CoordinatorCfp.startNotification();
						cfpEnd = call Superframe.getCfpEnd(&coordinatorSuperframe);
						if (cfpEnd < nextCommence) {
							call CfpAlarm.armAlarmClock(cfpEnd-82);
							waitingForCfpEnd = TRUE;
						}
					} else {
						// TODO: Below note could be a problem.
						// NOTE: Device CAP and CFP can be active in the idle period.
						// Idle period.
					}
					waitingForCapEnd = FALSE;
				} else if (waitingForCfpEnd) {
					// TODO: Below note could be a problem.
					// NOTE: Device CAP and CFP can be active in the idle period.
					// Idle period.
					waitingForCfpEnd = FALSE;
				}
				return SUCCESS;
			}
		#endif
	#endif
}
